use std::cmp::Ordering;
use std::fmt::Display;
use std::ops::Not;

use super::cast::As;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Error {
  DataTooLong,
  InvalidVersion,
  UnsupportedCharacterSet,
  InvalidEciDesignator,
  InvalidCharacter,
}

impl Display for Error {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let msg = match *self {
      Error::DataTooLong => "data too long",
      Error::InvalidVersion => "invalid version",
      Error::UnsupportedCharacterSet => "unsupported character set",
      Error::InvalidCharacter => "invalid character",
      Error::InvalidEciDesignator => "invalid eci designator",
    };
    f.write_str(msg)
  }
}

impl std::error::Error for Error {}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Color {
  Light,
  Dark,
}

impl Color {
  pub fn select<T>(self, dark: T, light: T) -> T {
    match self {
      Color::Light => light,
      Color::Dark => dark,
    }
  }
}

impl Not for Color {
  type Output = Self;
  fn not(self) -> Self::Output {
    match self {
      Color::Light => Color::Dark,
      Color::Dark => Color::Light,
    }
  }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
pub enum EcLevel {
  L = 0,
  M = 1,
  Q = 2,
  H = 3,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Version {
  Normal(i16),
  Micro(i16),
}

impl Version {
  pub fn width(self) -> i16 {
    match self {
      Version::Normal(v) => v * 4 + 17,
      Version::Micro(v) => v * 2 + 9,
    }
  }

  pub fn fetch<T>(self, ec_level: EcLevel, table: &[[T; 4]]) -> Result<T>
  where
    T: PartialEq + Default + Copy,
  {
    match self {
      Version::Normal(v @ 1..=40) => {
        return Ok(table[(v - 1).as_usize()][ec_level as usize])
      }
      Version::Micro(v @ 1..=4) => {
        let obj = table[(v + 39).as_usize()][ec_level as usize];
        if obj != T::default() {
          return Ok(obj);
        }
      }
      _ => {}
    }
    Err(Error::InvalidVersion)
  }

  pub fn mode_bits_count(self) -> usize {
    match self {
      Version::Micro(a) => (a - 1).as_usize(),
      _ => 4,
    }
  }

  pub fn is_micro(self) -> bool {
    match self {
      Version::Normal(_) => false,
      Version::Micro(_) => true,
    }
  }
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Mode {
  Numeric,
  Alphanumeric,
  Byte,
  Kanji,
}

impl Mode {
  pub fn length_bits_count(self, version: Version) -> usize {
    match version {
      Version::Micro(v) => {
        let v = v.as_usize();
        match self {
          Mode::Numeric => 2 + v,
          Mode::Alphanumeric | Mode::Byte => 1 + v,
          Mode::Kanji => v,
        }
      }

      Version::Normal(1..=9) => match self {
        Mode::Numeric => 10,
        Mode::Alphanumeric => 9,
        Mode::Byte | Mode::Kanji => 8,
      },

      Version::Normal(10..=26) => match self {
        Mode::Numeric => 12,
        Mode::Alphanumeric => 11,
        Mode::Byte => 16,
        Mode::Kanji => 10,
      },

      Version::Normal(_) => match self {
        Mode::Numeric => 14,
        Mode::Alphanumeric => 13,
        Mode::Byte => 16,
        Mode::Kanji => 12,
      },
    }
  }

  pub fn data_bits_count(self, raw_data_len: usize) -> usize {
    match self {
      Mode::Numeric => (raw_data_len * 10 + 2) / 3,
      Mode::Alphanumeric => (raw_data_len * 11 + 1) / 2,
      Mode::Byte => raw_data_len * 8,
      Mode::Kanji => raw_data_len * 13,
    }
  }

  pub fn max(self, other: Self) -> Self {
    match self.partial_cmp(&other) {
      Some(Ordering::Less) | Some(Ordering::Equal) => other,
      Some(Ordering::Greater) => self,
      None => Mode::Byte,
    }
  }
}

impl PartialOrd for Mode {
  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    match (*self, *other) {
      (Mode::Numeric, Mode::Alphanumeric)
      | (Mode::Numeric, Mode::Byte)
      | (Mode::Alphanumeric, Mode::Byte)
      | (Mode::Kanji, Mode::Byte) => Some(Ordering::Less),
      (Mode::Alphanumeric, Mode::Numeric)
      | (Mode::Byte, Mode::Numeric)
      | (Mode::Byte, Mode::Alphanumeric)
      | (Mode::Byte, Mode::Kanji) => Some(Ordering::Greater),
      (a, b) if a == b => Some(Ordering::Equal),
      _ => None,
    }
  }
}
